#define ADRENALIN_EXPORTS

#include "../../include/DirectX10/DirectX10Device.h"

/// Singleton Instance
Adrenalin::DirectX10Device* Adrenalin::Singleton<Adrenalin::DirectX10Device>::_pInstance = NULL;

namespace Adrenalin
{
	DirectX10Device::DirectX10Device()
		: _resolutionX(0), _resolutionY(0)
	{}
	// Func.: Init
	// Desc.: Initalisation of DirectX10
	EResult DirectX10Device::Init(HWND hWnd, int x, int y, bool windowed)
	{
		//Store Resolution
		_resolutionX = x;
		_resolutionY = y;

		//Set up DX swap chain
		DXGI_SWAP_CHAIN_DESC swapChainDesc;
		ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
	
		//set buffer dimensions and format
		swapChainDesc.BufferCount = 2;
		swapChainDesc.BufferDesc.Width = x;
		swapChainDesc.BufferDesc.Height = y;
		swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;;
	
		//set refresh rate
		swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	
		//sampling settings
		swapChainDesc.SampleDesc.Quality = 0;
		swapChainDesc.SampleDesc.Count = 1;

		//output window handle
		swapChainDesc.OutputWindow = hWnd;
		swapChainDesc.Windowed = true;  

		//Create the D3D device
		if ( FAILED( D3D10CreateDeviceAndSwapChain( NULL, 
													D3D10_DRIVER_TYPE_HARDWARE, 
													NULL, 
													0, 
													D3D10_SDK_VERSION, 
													&swapChainDesc, 
													&_swapChain, 
													&_d3DDevice ) ) ) 
		{
			WRITELOG(L"DirectX10 Device Creation failed", LOG_ERROR);
			return FAIL;
		}

		//try to get the back buffer
		ID3D10Texture2D* pBackBuffer;	
		if ( FAILED( _swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*) &pBackBuffer) ) ) 
		{
			WRITELOG(L"DirectX10 unable to get BackBuffer", LOG_ERROR);
			return FAIL;
		}

		//try to create render target view
		if ( FAILED( _d3DDevice->CreateRenderTargetView(pBackBuffer, NULL, &_renderTargetView) ) ) 
		{
			WRITELOG(L"DirectX10 unable to create RenderTargetView", LOG_ERROR);
			return FAIL;
		}

		//release the back buffer
		pBackBuffer->Release();

		//set the render target
		_d3DDevice->OMSetRenderTargets(1, &_renderTargetView, NULL);

		//create viewport structure	
		_viewPort.Width = x;
		_viewPort.Height = y;
		_viewPort.MinDepth = 0.0f;
		_viewPort.MaxDepth = 1.0f;
		_viewPort.TopLeftX = 0;
		_viewPort.TopLeftY = 0;

		//set the viewport
		_d3DDevice->RSSetViewports(1, &_viewPort);

		//set rasterizer state	
		D3D10_RASTERIZER_DESC rasterizerState;
		rasterizerState.CullMode = D3D10_CULL_NONE;
		rasterizerState.FillMode = D3D10_FILL_SOLID;
		rasterizerState.FrontCounterClockwise = true;
	    rasterizerState.DepthBias = false;
		rasterizerState.DepthBiasClamp = 0;
		rasterizerState.SlopeScaledDepthBias = 0;
		rasterizerState.DepthClipEnable = true;
		rasterizerState.ScissorEnable = false;
		rasterizerState.MultisampleEnable = false;
		rasterizerState.AntialiasedLineEnable = true;		

		WRITELOG(L"DirectX10 Device loaded", LOG_INFO);
		return OK;
	}
	// Func.: Render
	// Desc.: Render a single frame
	void DirectX10Device::BeginRender()
	{
		//clear scene
		_d3DDevice->ClearRenderTargetView( _renderTargetView, D3DXCOLOR(0,0,0.5,0) );
	}
	// Func.: EndRender
	// Desc.: Called after rendering
	void DirectX10Device::EndRender()
	{
		//flip buffers
		_swapChain->Present(0,0);
	}
	// Func.: Close
	// Desc.: Cleanup of all DX11 Instances
	void DirectX10Device::Close()
	{
		WRITELOG(L"DirectX10 Device closed", LOG_INFO);
	}

	void DirectX10Device::SetPerspectiveFovLH( Math::Vector3<float>& eye, Math::Vector3<float>& at, Math::Vector3<float>& up, float n, float f )
	{
		// Set up the view matrix
		D3DXMatrixLookAtLH(&_viewMatrix, &(D3DXVECTOR3)eye, &(D3DXVECTOR3)at, &(D3DXVECTOR3)up);
		
		//Set up projection matrix
		//--------------------------------------------------------------
		D3DXMatrixPerspectiveFovLH(&_projectionMatrix, (float)D3DX_PI * 0.5f, (float)_resolutionX/(float)_resolutionY, n, f);

		
		WRITELOG(L"Camera Init complete", LOG_INFO);
	}

	void DirectX10Device::LoadEffectFile()
	{
		if ( FAILED( D3DX10CreateEffectFromFile(
			L"./data/shaders/basicEffect.fx", 
			NULL, NULL, 
			"fx_4_0", 
			D3D10_SHADER_ENABLE_STRICTNESS, 
			0, 
			_d3DDevice, 
			NULL, 
			NULL, 
			&_pBasicEffect, 
			NULL, 
			NULL	) ) ) 
		{ 
			WRITELOG(L"Unable to load FX File", LOG_ERROR);	
		}

		_pBasicTechnique = _pBasicEffect->GetTechniqueByName("Render");
	
		//create matrix effect pointers
		_pViewMatrixEffectVariable = _pBasicEffect->GetVariableByName( "View" )->AsMatrix();
		_pProjectionMatrixEffectVariable = _pBasicEffect->GetVariableByName( "Projection" )->AsMatrix();
		_pWorldMatrixEffectVariable = _pBasicEffect->GetVariableByName( "World" )->AsMatrix();

	}

	void DirectX10Device::CreateVertex()
	{
		D3D10_INPUT_ELEMENT_DESC layout[] = 
		{	
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
			{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 }
		};
		UINT numElements = 2;

		D3D10_PASS_DESC PassDesc;
		_pBasicTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );
		
		_d3DDevice->CreateInputLayout( layout, 
										numElements, 
										PassDesc.pIAInputSignature,
										PassDesc.IAInputSignatureSize, 
										&_pVertexLayout );

		// Set the input layout
		_d3DDevice->IASetInputLayout( _pVertexLayout );

		//create vertex buffer (space for 100 vertices)
		UINT numVertices = 100;

		D3D10_BUFFER_DESC bd;
		bd.Usage = D3D10_USAGE_DYNAMIC;
		bd.ByteWidth = sizeof( vertex ) * numVertices;
		bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
		bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
		bd.MiscFlags = 0;

		if ( FAILED( _d3DDevice->CreateBuffer( 
			&bd, NULL, &_pVertexBuffer ) ) ) 
		{ 
			WRITELOG(L"Unable to create VertexBuffer", LOG_ERROR);
		}

		// Set vertex buffer
		UINT stride = sizeof( vertex );
		UINT offset = 0;
		_d3DDevice->IASetVertexBuffers( 0, 1, &_pVertexBuffer, &stride, &offset );	

	}

	void DirectX10Device::RenderVertex()
	{
		//create world matrix
		static float r;
		D3DXMATRIX w;
		D3DXMatrixIdentity(&w);
		D3DXMatrixRotationY(&w, r);
		r += 0.001f;

		//set effect matrices
		_pWorldMatrixEffectVariable->SetMatrix(w);
		_pViewMatrixEffectVariable->SetMatrix(_viewMatrix);
		_pProjectionMatrixEffectVariable->SetMatrix(_projectionMatrix);

		//fill vertex buffer with vertices
		UINT numVertices = 3;	
		vertex* v = NULL;	

		//lock vertex buffer for CPU use
		_pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**) &v );
	
		v[0] = vertex( D3DXVECTOR3(-1,-1,0), D3DXVECTOR4(1,0,0,1) );
		v[1] = vertex( D3DXVECTOR3(0,1,0), D3DXVECTOR4(0,1,0,1) );
		v[2] = vertex( D3DXVECTOR3(1,-1,0), D3DXVECTOR4(0,0,1,1) );

		_pVertexBuffer->Unmap();

		// Set primitive topology 
		_d3DDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP );

		//get technique desc
		D3D10_TECHNIQUE_DESC techDesc;
		_pBasicTechnique->GetDesc( &techDesc );
	
		for( UINT p = 0; p < techDesc.Passes; ++p )
		{
			//apply technique
			_pBasicTechnique->GetPassByIndex( p )->Apply( 0 );
				
			//draw
			_d3DDevice->Draw( numVertices, 0 );
		}
	}
};